package com.lipaluma.field.mapper.factory;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.mapper.multiple.MultipleFieldMapper;
import com.lipaluma.field.mapper.multiple.impl.AggregateMultipleFieldMapper;
import com.lipaluma.field.mapper.multiple.impl.ConcatMultipleFieldMapper;

/**
 * A static class that provides utility methods that allow instantiating a column by reflection.
 * 
 * @author mario.soares
 */
public class MultipleFieldMapperFactory {
	private static Map<Class<?>, Class<? extends MultipleFieldMapper>> mapMultipleFieldMapperType = new HashMap<Class<?>, Class<? extends MultipleFieldMapper>>();
	
	public static Class<? extends MultipleFieldMapper> defaultMultipleFieldMapperForCollection= AggregateMultipleFieldMapper.class;
	public static Class<? extends MultipleFieldMapper> defaultMultipleFieldMapperForArray = AggregateMultipleFieldMapper.class;

	static {
		mapMultipleFieldMapperType.put(String.class, ConcatMultipleFieldMapper.class);
	}

	/**
	 * create a MapColumn by instantiate clazz
	 * @param clazz the column class to instantiate
	 * @return a column of type clazz
	 */
	public static MultipleFieldMapper instantiateMultipleFieldMapper(Class<? extends MultipleFieldMapper> clazz) {
		try {
			return clazz.newInstance();
		} catch (InstantiationException ex) {
			throw new CsvParserException("The class "+clazz+" can't be instanciated.", ex);
		} catch (IllegalAccessException ex) {
			throw new CsvParserException("The class "+clazz+" is not accessible.", ex);
		}
	}
	
	/**
	 * create a column by instantiate clazzname
	 * @param clazzname the column class to instantiate
	 * @return a column of type clazzname
	 */
	public static MultipleFieldMapper instantiateMultipleFieldMapper(String clazzname) {
		try {
			return (MultipleFieldMapper)Class.forName(clazzname).newInstance();
		} catch (ClassCastException ex) {
			throw new CsvParserException("The class "+clazzname+" is not a column.", ex);
		} catch (InstantiationException ex) {
			throw new CsvParserException("The class "+clazzname+" can't be instanciated.", ex);
		} catch (IllegalAccessException ex) {
			throw new CsvParserException("The class "+clazzname+" is not accessible.", ex);
		} catch (ClassNotFoundException ex) {
			throw new CsvParserException("The class "+clazzname+" is not found.", ex);
		}
	}

	/**
	 * define the default column for Collection type
	 * 
	 * @param columnClass column class that will be assigned to the Collection type by default
	 */
	public static void defineDefaultMulitpleFieldMapperForCollection(Class<? extends MultipleFieldMapper> columnClass) {
		defaultMultipleFieldMapperForCollection = columnClass;
	}

	/**
	 * define the default column for Array type
	 * 
	 * @param columnClass column class that will be assigned to the array type by default
	 */
	public static void defineDefaultMulipleFieldMapperForArray(Class<? extends MultipleFieldMapper> columnClass) {
		defaultMultipleFieldMapperForArray = columnClass;
	}
	
	/**
	 * add a new type with his column who maps the csv especially for that type.
	 * 
	 * @param name the name of the type upper case
	 * @param columnClass class of the column
	 */
	public static void setMultipleFieldMapperForType(Class<?> clazz, Class<? extends MultipleFieldMapper> columnClass) {
		mapMultipleFieldMapperType.put(clazz, columnClass);
	}

	/**
	 * returns the class defined by the type name
	 * 
	 * @param name the name of the type (upper case)
	 * @return the column class corresponding
	 */
	public static Class<? extends MultipleFieldMapper> retrieveMultipleFieldMapperClassForType(Class<?> clazz) {
		Class<? extends MultipleFieldMapper> fieldMapperClass = mapMultipleFieldMapperType.get(clazz);
		if(fieldMapperClass == null) {
			if(clazz.isArray())
				fieldMapperClass = defaultMultipleFieldMapperForArray;
			else if(Collection.class.isAssignableFrom(clazz))
				fieldMapperClass = defaultMultipleFieldMapperForCollection;
		}
		return fieldMapperClass;
	}

	/**
	 * verify if a column exists for the type name
	 * 
	 * @param name the name of the type (upper case)
	 * @return true if exists
	 */
	public static boolean isMultipleFieldMapperExistsForType(Class<?> clazz) {
		return mapMultipleFieldMapperType.containsKey(clazz) ||
				clazz.isArray() || Collection.class.isAssignableFrom(clazz);
	}

	/**
	 * instantiate and returns the column class for the type name defined
	 * 
	 * @param name the name of the type (upper case)
	 * @return a new instance of the column class corresponding
	 */
	public static MultipleFieldMapper createMultipleFieldMapperForType(Class<?> clazz) {
		String name = clazz.getSimpleName();
		if(!isMultipleFieldMapperExistsForType(clazz))
			throw new CsvParserException("can't instantiate Mulitple FieldMapper for class "+name+".");
		Class<? extends MultipleFieldMapper> clazzColumn = retrieveMultipleFieldMapperClassForType(clazz);
		if(clazzColumn != null)
			return instantiateMultipleFieldMapper(clazzColumn);
		else
			throw new CsvParserException("can't instantiate Mulitple FieldMapper for class "+name+".");
	}

}
